﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls.Maps;
using System.Linq;
using System.Collections.Generic;
using OptymalizatorTrasy.RouteService;
using System.Collections.ObjectModel;
using Microsoft.Phone.Controls.Maps.Platform;
using System.Diagnostics;
using System.ServiceModel;

namespace OptymalizatorTrasy
{
    public class Komiwojazer
    {
        
        private const double prawdopSkrzyzowania = 0.1;
        private const double prawdopMutacji = 0.3;
        private int[] osobnikNajlepszy;
        private int[] osobnikNajgorszy;

        private MapLayer layerP;
        private MapLayer layerT;
        private int liczbaPunktow;
        private String ApplicationId = "ArEkpLZpUL4tqiyh7PBErNyP1NQ3XVMK1xrWSP8UsOkKyJ-2vwHEZAm60iz1bNxv";
        private TextBlock dlugoscTrasy;
        private int wielkoscPopulacji;
        private int iloscIteracji;

        public Komiwojazer(MapLayer layerP, MapLayer layerT)
        {
            this.layerT = layerT;
            this.layerP = layerP;
            Pushpin pom = new Pushpin();
            pom.Location = ((Pushpin)layerP.Children.First()).Location; //duplikacja naszej pozycji -> tak aby algorytm zatoczył kółko 
            // layer.Children.Add(pom);
            this.liczbaPunktow = layerP.Children.Count;
            wielkoscPopulacji = liczbaPunktow * 20;
            iloscIteracji = liczbaPunktow * 20;

            foreach (Pushpin a in layerP.Children)
            {
                Debug.WriteLine("Pineska: " + a.Content);
            }
        }

        /*
         * funkcje pomocnicze dla algorytmu komiwojazera
         */
        #region funkcjePomocnicze
        private double[,] liczOdleglosc()
        {
            double[,] odleglosc = new double[liczbaPunktow, liczbaPunktow];

            for (int i = 0; i < liczbaPunktow; i++)
            {
                for (int j = 0; j <= i; j++)
                {
                    if (i != j)
                    {
                        Pushpin pom1 = layerP.Children[i] as Pushpin;
                        Pushpin pom2 = layerP.Children[j] as Pushpin;
                        odleglosc[i, j] = Math.Sqrt(Math.Pow(pom1.Location.Latitude * Math.Cos(pom1.Location.Longitude * 180 / Math.PI) - pom2.Location.Latitude * Math.Cos(pom2.Location.Longitude * 180 / Math.PI), 2) + Math.Pow(pom1.Location.Longitude - pom2.Location.Longitude, 2));
                        odleglosc[j, i] = odleglosc[i, j];
                    }
                    else
                    {
                        odleglosc[i, j] = 0;
                    }
                }
            }

            return odleglosc;
        }

        private int[,] stworzPopulacje()
        {
            int[,] osobniki = new int[wielkoscPopulacji, liczbaPunktow];
            Random random = new Random();

            for (int i = 0; i < wielkoscPopulacji; i++)
            {
                for (int j = liczbaPunktow - 1; j >= 0; j--) osobniki[i, j] = random.Next(liczbaPunktow - j);
            }

            return osobniki;
        }

        private double liczPrzystosowanie(int[] pom2, double[,] odleglosc)
        {
            double suma = 0;

            for (int i = 0; i < pom2.Length - 1; i++)
            {
                suma += odleglosc[pom2[i], pom2[i + 1]];
            }

            suma += odleglosc[pom2.Last(), pom2.First()];

            return 1 / suma;
        }

        private double[] przystosowanie(int[,] osobniki, double[,] odleglosc)
        {
            double[] funkcjaPrzystosowania = new double[wielkoscPopulacji];
            List<int> pom = new List<int>();
            int[] pom2;

            for (int j = 0; j < wielkoscPopulacji; j++)
            {
                pom.Clear();
                for (int i = 0; i < liczbaPunktow; i++) pom.Add(i);
                pom2 = new int[liczbaPunktow];

                for (int k = 0; k < liczbaPunktow; k++)
                {

                    pom2[k] = pom[osobniki[j, k]];
                    pom.RemoveAt(osobniki[j, k]);

                }

                funkcjaPrzystosowania[j] = liczPrzystosowanie(pom2, odleglosc);

            }
            return funkcjaPrzystosowania;
        }

        private double[] prawdopodobienstwo(double[] funkcjaPrzystosowania)
        {
            double[] prstwoWylosowania = new double[wielkoscPopulacji];
            double sumaPrzystosowan = funkcjaPrzystosowania.Sum();

            prstwoWylosowania[0] = funkcjaPrzystosowania[0] / sumaPrzystosowan;

            for (int j = 1; j < wielkoscPopulacji; j++)
            {
                prstwoWylosowania[j] = prstwoWylosowania[j - 1] + funkcjaPrzystosowania[j] / sumaPrzystosowan;
            }

            return prstwoWylosowania;
        }

        private int[,] krzyzowanie(int[,] osobniki)
        {
            Random random = new Random();
            int[,] osobnik_pom = new int[2, liczbaPunktow];
            int polowa = (int)(liczbaPunktow / 2);

            for (int j = 0; j < wielkoscPopulacji; j += 2)
            {

                if (random.NextDouble() < prawdopSkrzyzowania)
                {
                    for (int k = 0; k < polowa; k++)
                    {
                        osobnik_pom[0, k] = osobniki[j, k];
                        osobnik_pom[1, k] = osobniki[j + 1, k];
                        osobnik_pom[0, liczbaPunktow - k - 1] = osobniki[j, liczbaPunktow - k - 1];
                        osobnik_pom[1, liczbaPunktow - k - 1] = osobniki[j + 1, liczbaPunktow - k - 1];
                    }

                    for (int k = 0; k < liczbaPunktow; k++)
                    {
                        osobniki[j, k] = osobnik_pom[0, k];
                        osobniki[j + 1, k] = osobnik_pom[1, k];
                    }
                }
            }

            return osobniki;
        }

        private int[,] mutacje(int[,] osobniki)
        {
            Random random = new Random();
            int podmieniana_pozycja;
            int wartosc_do_podmiany;

            for (int j = 0; j < wielkoscPopulacji; j++)
            {

                if (random.NextDouble() < prawdopMutacji)
                {
                    podmieniana_pozycja = (int)random.Next(liczbaPunktow);
                    wartosc_do_podmiany = (int)random.Next(liczbaPunktow - podmieniana_pozycja);
                    osobniki[j, podmieniana_pozycja] = wartosc_do_podmiany;
                }
            }

            return osobniki;
        }

        private int[,] nowaPopulacja(int[,] osobniki, double[] prstwoWylosowania)
        {
            int index = 0;
            int[,] noweOsobniki = new int[wielkoscPopulacji, liczbaPunktow];
            Random random = new Random();

            for (int j = 0; j < wielkoscPopulacji; j++)
            {

                while (random.NextDouble() > prstwoWylosowania[index])
                {
                    index++;
                }

                for (int k = 0; k < liczbaPunktow; k++) noweOsobniki[j, k] = osobniki[index, k];
                index = 0;

            }

            return noweOsobniki;

        }

        private int[] znajdzNajlepszy(int[] osobnikNajlepszy)
        {
            int[] pom = new int[liczbaPunktow];

            List<int> pom1 = new List<int>();
            for (int i = 0; i < liczbaPunktow; i++) pom1.Add(i);

            for (int k = 0; k < liczbaPunktow; k++)
            {
                pom[k] = pom1[osobnikNajlepszy[k]];
                pom1.RemoveAt(osobnikNajlepszy[k]);

            }

            return pom;

        }

        private int znajdzIndexNajlepszy(double[] funkcjaPrzystosowania, int[,] osobniki)
        {
            double max = 0;
            int indexNajlepszy = 0;

            for (int i = 0; i < wielkoscPopulacji; i++)
            {
                if (funkcjaPrzystosowania[i] > max)
                {
                    max = funkcjaPrzystosowania[i];
                    indexNajlepszy = i;
                    if (max == funkcjaPrzystosowania.Max()) break;
                }
            }

            return indexNajlepszy;

        }

        private int znajdzNajgorszy(double[] funkcjaPrzystosowania, int[,] osobniki)
        {
            int[] osobnikNajgorszy = new int[liczbaPunktow];
            double min = 100;
            int indexNajgorszy = 0;


            for (int i = 0; i < wielkoscPopulacji; i++)
            {
                if (funkcjaPrzystosowania[i] < min)
                {
                    min = funkcjaPrzystosowania[i];
                    indexNajgorszy = i;
                    if (min == funkcjaPrzystosowania.Min()) break;
                }
            }

            return indexNajgorszy;

        }
        #endregion


        /*
         * implementacja algorytmu komiwojażera
         * Na wyjściu zwraca najlepszego osobnika w postaci tablicy indeksów dla MapLayera
         */
        public void algorytm()
        {
            //inicjalizacja potrzebnych do algorytmu tablic
            double[,] odleglosc = null;
            int[,] osobniki = null;
            double[] prstwoWylosowania = null;
            double[] funkcjaPrzystosowania = null;
            osobnikNajlepszy = new int[liczbaPunktow];

            //wyliczenie tablicy odleglości
            odleglosc = liczOdleglosc();

            //stworzenie losowej populacji
            osobniki = stworzPopulacje();

            //iteracje algorytmu
            for (int i = 0; i < iloscIteracji; i++)
            {
                //wyznaczamy wielkość funkcji przystosowania oraz sumę wszystkich przystosowań
                funkcjaPrzystosowania = przystosowanie(osobniki, odleglosc);

                //wyliczenie skumulowanego prawdopodobieństwa wylosowania
                prstwoWylosowania = prawdopodobienstwo(funkcjaPrzystosowania);

                //znalezienie najlepiej dopasowanego osobnika
                int aaaa = znajdzIndexNajlepszy(funkcjaPrzystosowania, osobniki);

                for (int a = 0; a < liczbaPunktow; a++)
                {
                    osobnikNajlepszy[a] = osobniki[aaaa, a];

                }

                //stworzenie nowej populacji poprzez losowanie
                osobniki = nowaPopulacja(osobniki, prstwoWylosowania);

                //krzyżowanie
                osobniki = krzyzowanie(osobniki);

                //mutacje
                osobniki = mutacje(osobniki);

                //zastąpienie osobnika najsłabszego najlepszym
                int aaa = znajdzNajgorszy(funkcjaPrzystosowania, osobniki);
                for (int a = 0; a < liczbaPunktow; a++)
                {
                    osobniki[aaa, a] = osobnikNajlepszy[a];
                }

            }

            //znalezienie najlepiej dopasowanego osobnika
            osobnikNajlepszy = znajdzNajlepszy(osobnikNajlepszy);

        }


        internal void Trasa(TextBlock dlugoscTrasy)
        {
            this.dlugoscTrasy = dlugoscTrasy;
            RouteRequest routeRequest = new RouteRequest(); //zapytanie o długość drogi do serwera
            routeRequest.Credentials = new Credentials();
            routeRequest.Credentials.ApplicationId = ApplicationId;
            routeRequest.Waypoints = new ObservableCollection<Waypoint>(); //stworzenie kolekcji punktów będącymi lokalizacjami

            for (int k = 0; k < liczbaPunktow; k++) //zapełnienie jej lokalizacjami naszych punktów
            {
                Waypoint p = new Waypoint();
                p.Location = ((Pushpin)layerP.Children[osobnikNajlepszy[k]]).Location;
                routeRequest.Waypoints.Add(p);
                Debug.WriteLine("" + p.Location.Latitude + "\n");
            }

            Waypoint p1 = new Waypoint();

            p1.Location = ((Pushpin)layerP.Children[osobnikNajlepszy[0]]).Location;
            Debug.WriteLine("" + p1.Location.Latitude + "\n");
            routeRequest.Waypoints.Add(p1);

            //ustawienie opcji zapytania do serwera
            routeRequest.Options = new RouteOptions();
            routeRequest.Options.RoutePathType = RoutePathType.Points;
            routeRequest.UserProfile = new OptymalizatorTrasy.RouteService.UserProfile();
            routeRequest.UserProfile.DistanceUnit = OptymalizatorTrasy.RouteService.DistanceUnit.Kilometer;

            //wykonanie zapytania do serwera  
            RouteServiceClient routeClient = new RouteServiceClient("BasicHttpBinding_IRouteService");
            routeClient.CalculateRouteCompleted += new EventHandler<CalculateRouteCompletedEventArgs>(routeService_CalculateRouteCompleted);
            routeClient.CalculateRouteAsync(routeRequest); 


        }

        void routeService_CalculateRouteCompleted(object sender, CalculateRouteCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                if (e.Error is FaultException)
                {
                    MessageBox.Show("Nie można wyznaczyć trasy - brak dojazdu do wyznaczenoge punktu");
                }
                else
                {
                    MessageBox.Show("Error: " + e.Error.ToString());
                }
            }     
            else{
                if (e.Result != null)
                {

                    MapPolyline routeLine = new MapPolyline(); //stworzenie MapPolyLine i ustawienie jego parametrów
                    routeLine.Locations = new LocationCollection();
                    routeLine.Stroke = new SolidColorBrush(Colors.Blue);
                    routeLine.Opacity = 0.50;
                    routeLine.StrokeThickness = 5.0;

                    foreach (Location p in e.Result.Result.RoutePath.Points)
                    {
                        routeLine.Locations.Add(new Location { Latitude = p.Latitude, Longitude = p.Longitude });
                    }
                    routeLine.Locations.Add(new Location { Latitude = e.Result.Result.RoutePath.Points[0].Latitude, Longitude = e.Result.Result.RoutePath.Points[0].Longitude });

                    layerT.Children.Add(routeLine);

                    dlugoscTrasy.Text = "Długość trasy: " + Convert.ToString(e.Result.Result.Summary.Distance); //DODAć odleglosc!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! od ostatniego do pierwszego
                    dlugoscTrasy.Visibility = Visibility.Visible;

                }
            }
        }
    }
}
